/***************************************************************************
 *   This is a part of the NextCAD Web APP.                                *
 *   Copyright (C) AMCAX, All rights reserved.                             *
 ***************************************************************************/

import { pickedLineData, pickedVertexData } from '@amcax/base';
import {
  NumberInputFormat,
  PickInputRJSFFormat,
} from '@app-cad/common/components/command-form/constants';
import { PickableCommandForm } from '@app-cad/common/components/command-form/pickable';
import { useCommand } from '@app-cad/common/hooks/useCommand';
import { useTools } from '@app-cad/common/hooks/useTools';
import { getIdOfPickedObj, previewData } from '@app-cad/common/utils/render';
import { previewer, ctx2, dataManager } from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { FeatureBaseCommand, FeaturedCmdDialogParams } from '@app-cad/types';
import { useAsyncEffect } from 'ahooks';
import i18n from 'i18next';
import { useMemo, useState } from 'react';
import {
  Vector3,
  BufferGeometry,
  LineBasicMaterial,
  LineSegments,
  Float32BufferAttribute,
} from 'three';

enum PosType {
  CENTER = 'center',
  BOTTOMCENTER = 'bottom-center',
  BOTTOMVERTEX = 'bottom-vertex',
}

enum InputMode {
  PICK = 'pick',
  COORDINATE = 'coordinate',
}

enum BoxMode {
  BOX = 'box',
  FLAT = 'flat',
}

type FormData = {
  length: number;
  width: number;
  height: number;
  postype: PosType;
  inputMode: InputMode;
  boxMode: BoxMode;
  origin?: pickedVertexData;
  x?: number;
  y?: number;
  z?: number;
  zdir: pickedLineData;
  reverse: boolean;
  reverseLength: boolean;
  reverseWidth: boolean;
  nx?: number;
  ny?: number;
  nz?: number;
};

type Params = {
  length: number;
  width: number;
  height: number;
  frame?: string | Array<number>;
  postype: PosType;
  origin: string | Array<number>;
  boxMode: BoxMode;
  zdir: string;
  reverse: boolean;
  reverseLength: boolean;
  reverseWidth: boolean;
  nx: number;
  ny: number;
  nz: number;
};

const FlatNumber = 0.000001;

const determineAxis = (pointArr1, pointArr2) => {
  const [x1, y1, z1] = pointArr1,
    [x2, y2, z2] = pointArr2;

  return y1 !== y2 && x1 === x2 && z1 === z2
    ? 'y'
    : z1 !== z2 && x1 === x2 && y1 === y2
      ? 'z'
      : 'x';
};

export const isSameLocation = (pointArr1, pointArr2) => {
  const [x1, y1, z1] = pointArr1,
    [x2, y2, z2] = pointArr2;

  return (
    Math.abs(x1 - x2) < FlatNumber &&
    Math.abs(y1 - y2) < FlatNumber &&
    Math.abs(z1 - z2) < FlatNumber
  );
};

export const createCrossByPointArray = (pointArrX, pointArrY, pointArrZ) => {
  const positions = [-2, -2, 0, 2, 2, 0, -2, 2, 0, 2, -2, 0];
  const allPositions = [];
  pointArrX.forEach((pointA) => {
    pointArrY.forEach((pointB) => {
      pointArrZ.forEach((pointC) => {
        const position = new Vector3(pointA.x, pointB.y, pointC.z);
        for (let j = 0; j < positions.length; j += 3) {
          allPositions.push(
            positions[j] + position.x,
            positions[j + 1] + position.y,
            positions[j + 2] + position.z,
          );
        }
      });
    });
  });
  const combinedGeometry = new BufferGeometry();
  combinedGeometry.setAttribute(
    'position',
    new Float32BufferAttribute(allPositions, 3),
  );

  const lineMaterial = new LineBasicMaterial({ color: 0xff0000 });
  const combinedLines = new LineSegments(combinedGeometry, lineMaterial);
  combinedLines.renderOrder = -1;
  ctx2.cadScene.toolsGroup.add(combinedLines);
};

export const createCrossByEdges = (pointArr1, pointArr2, { nx, ny, nz }) => {
  const dir = determineAxis(pointArr1, pointArr2);
  const n = { x: nx, y: ny, z: nz }[dir];
  const point1 = new Vector3(...pointArr1);
  const point2 = new Vector3(...pointArr2);
  const direction = point2.clone().sub(point1).normalize();
  const distance = point1.distanceTo(point2);
  const step = distance / (n - 1);

  const positions = [];
  for (let i = 0; i < n; i++) {
    const position = point1
      .clone()
      .add(direction.clone().multiplyScalar(step * i));

    positions.push(position);
  }
  return { positions, dir };
};

export const getPointPosition = (point) => {
  if (point === 'ORIGIN') {
    return [0, 0, 0];
  }
  let origin = [];
  if (Array.isArray(point)) {
    origin = point;
  } else {
    const objectInfo = dataManager.getObjectInfoByDxid(point);
    origin = objectInfo.params.points[0];
  }
  return origin;
};

const clearCross = () => {
  const group = ctx2.cadScene.toolsGroup;
  for (let i = group.children.length - 1; i >= 0; i--) {
    const child = group.children[i];
    if (child instanceof LineSegments) {
      group.remove(child);
      child.geometry.dispose(); // 释放几何体内存
      child.material.dispose(); // 释放材质内存
    }
  }
};

function ElecBox({ title, onClose, props }: FeaturedCmdDialogParams) {
  const initData: FormData = {
    length: 100,
    width: 100,
    height: FlatNumber,
    postype: PosType.BOTTOMVERTEX,
    inputMode: InputMode.PICK,
    boxMode: BoxMode.FLAT,
    origin: null,
    x: 0,
    y: 0,
    z: 0,
    zdir: null,
    reverse: false,
    reverseLength: false,
    reverseWidth: false,
    nx: 2,
    ny: 2,
    nz: 2,
  };
  const [formData, setFormData] = useState<FormData>(initData);

  const { picker } = useTools();

  useAsyncEffect(async () => {
    if (props == null) {
      setFormData(initData);
      return;
    }

    const params: Params = props.params;
    const {
      length,
      width,
      height,
      postype,
      origin,
      zdir,
      reverse,
      reverseLength,
      reverseWidth,
      nx,
      ny,
      nz,
    } = params;

    await initEditDependency([Array.isArray(origin) ? null : origin, zdir]);

    let datas = {} as FormData;
    if (Array.isArray(origin)) {
      const ori = origin as Array<number>;
      datas = {
        length: length,
        width: width,
        height: height,
        postype: postype,
        inputMode: InputMode.COORDINATE,
        boxMode: FlatNumber === height ? BoxMode.FLAT : BoxMode.BOX,
        x: ori[0],
        y: ori[1],
        z: ori[2],
        zdir: picker.generatePickObjByPickID(zdir) as pickedLineData,
        reverse: reverse,
        reverseLength: reverseLength,
        reverseWidth: reverseWidth,
        nx: nx,
        ny: ny,
        nz: nz,
      };
    } else {
      datas = {
        length: length,
        width: width,
        height: height,
        postype: postype,
        inputMode: InputMode.PICK,
        boxMode: FlatNumber === height ? BoxMode.FLAT : BoxMode.BOX,
        origin: picker.generatePickObjByPickID(
          origin as string,
        ) as pickedVertexData,
        zdir: picker.generatePickObjByPickID(zdir) as pickedLineData,
        reverse: reverse,
        reverseLength: reverseLength,
        reverseWidth: reverseWidth,
        nx: nx,
        ny: ny,
        nz: nz,
      };
    }

    setFormData(datas);
  }, []);

  useAsyncEffect(async () => {
    if (!isFormDataValid()) {
      previewer.clear();
      return;
    }
    await execute(true);
  }, [formData]);

  useAsyncEffect(async () => {
    const { height } = formData;
    setFormData({
      ...formData,
      height:
        formData.boxMode === BoxMode.BOX
          ? height != FlatNumber
            ? height
            : 100
          : FlatNumber,
    });
    return schema;
  }, [formData.boxMode]);

  const isFormDataValid = () => {
    const { length, width, height, origin, zdir, inputMode, x, y, z } =
      formData;
    if (inputMode === InputMode.PICK) {
      return (
        length !== null &&
        width !== null &&
        height !== null &&
        origin !== null &&
        zdir !== null
      );
    } else {
      return (
        length !== null &&
        width !== null &&
        height !== null &&
        x !== null &&
        y !== null &&
        z !== null &&
        zdir !== null
      );
    }
  };
  const finishValid = useMemo(isFormDataValid, [formData]);
  const checkAndFinish = async () => {
    if (!isFormDataValid()) {
      return;
    }

    await execute(false);

    onClose();
  };

  const { initEditDependency } = useCommand('cmd-elec-box', {
    onClose,
    finishDraw: checkAndFinish,
  });

  const execute = async (preview: boolean = false) => {
    const {
      length,
      width,
      height,
      postype,
      origin,
      boxMode,
      zdir,
      reverse,
      reverseLength,
      reverseWidth,
      x,
      y,
      z,
      inputMode,
      nx,
      ny,
      nz,
    } = formData;
    let originObj;
    if (inputMode === InputMode.PICK) {
      originObj = getIdOfPickedObj(origin);
    } else {
      originObj = [x, y, z];
    }
    const zID = getIdOfPickedObj(zdir);
    const params: Params = {
      length: length,
      width: width,
      height: height,
      postype: postype,
      origin: originObj,
      boxMode: boxMode,
      zdir: zID,
      reverse: reverse,
      reverseLength: reverseLength,
      reverseWidth: reverseWidth,
      nx: nx,
      ny: ny,
      nz: nz,
    };
    const res =
      props == null
        ? await wsApi.createPrimitiveNew('Box', params, preview)
        : await wsApi.setParams((props as any).shape, params, preview);

    if (preview) {
      await previewData(res);
    }

    if (res?.length > 0 && res[0]?.edges) {
      clearCross();
      const { nx, ny, nz } = formData;
      const pointXYZ = { x: [], y: [], z: [] };
      const origin = getPointPosition(params.origin);
      res[0].edges
        .filter(
          (item) =>
            isSameLocation(item.mesh[0], origin) ||
            isSameLocation(item.mesh[1], origin),
        )
        .forEach((item) => {
          const { positions, dir } = createCrossByEdges(
            item.mesh[0],
            item.mesh[1],
            { nx, ny, nz },
          );
          pointXYZ[dir] = positions;
        });
      createCrossByPointArray(pointXYZ.x, pointXYZ.y, pointXYZ.z);
    }

    return res;
  };

  const schema = useMemo(() => {
    const boxSchema = {
      type: 'string',
      title: '面体选择',
      default: BoxMode.FLAT,
      oneOf: [
        {
          type: 'string',
          title: '面电场选取',
          const: BoxMode.FLAT,
        },
        {
          type: 'string',
          title: '体电场选取',
          const: BoxMode.BOX,
        },
      ],
    };

    const modeSchema = {
      type: 'string',
      title: '定位点输入方式',
      default: InputMode.PICK,
      oneOf: [
        {
          type: 'string',
          title: '拾取点',
          const: InputMode.PICK,
        },
        {
          type: 'string',
          title: '点坐标',
          const: InputMode.COORDINATE,
        },
      ],
    };

    const typeSchema = {
      type: 'string',
      title: '定位方式',
      default: PosType.BOTTOMVERTEX,
      oneOf: [
        // {
        //   type: 'string',
        //   title: '底面中心',
        //   const: PosType.BOTTOMCENTER,
        // },
        {
          type: 'string',
          title: '底部顶点',
          const: PosType.BOTTOMVERTEX,
        },
        // {
        //   type: 'string',
        //   title: '体中心',
        //   const: PosType.CENTER,
        // },
      ],
    };

    const zID = getIdOfPickedObj(formData.zdir);

    // 拾取
    if (formData.inputMode === InputMode.PICK) {
      return {
        properties: {
          boxMode: boxSchema,
          postype: typeSchema,
          inputMode: modeSchema,
          origin: {
            type: 'string',
            format: PickInputRJSFFormat.PickVertex,
          },
          zdir: {
            type: 'string',
            format: PickInputRJSFFormat.PickDirection,
          },
          length: {
            type: 'number',
            title: '长度',
            default: 100,
            format: NumberInputFormat.Length,
          },
          width: {
            type: 'number',
            title: '宽度',
            default: 100,
            format: NumberInputFormat.Length,
          },
          ...(formData.boxMode === BoxMode.BOX
            ? {
                height: {
                  type: 'number',
                  title: '高度',
                  default: 100,
                  format: NumberInputFormat.Length,
                },
              }
            : {}),
          ...(formData.boxMode === BoxMode.FLAT && zID === 'YOZ'
            ? {}
            : {
                nx: {
                  type: 'number',
                  title: 'Nx横向点数',
                  default: 2,
                  minimum: 2,
                  maximum: 150,
                  format: NumberInputFormat.Real,
                },
              }),
          ...(formData.boxMode === BoxMode.FLAT && zID === 'XOZ'
            ? {}
            : {
                ny: {
                  type: 'number',
                  title: 'Ny纵向点数',
                  default: 2,
                  minimum: 2,
                  maximum: 150,
                  format: NumberInputFormat.Real,
                },
              }),
          ...(formData.boxMode === BoxMode.FLAT && zID === 'XOY'
            ? {}
            : {
                nz: {
                  type: 'number',
                  title: 'Nz竖向点数',
                  default: 2,
                  minimum: 2,
                  maximum: 150,
                  format: NumberInputFormat.Real,
                },
              }),
        },
      };
    }

    // 坐标值
    if (formData.inputMode === InputMode.COORDINATE) {
      return {
        properties: {
          boxMode: boxSchema,
          postype: typeSchema,
          inputMode: modeSchema,
          x: {
            type: 'number',
            title: 'x',
            default: 0,
            format: NumberInputFormat.Length,
          },
          y: {
            type: 'number',
            title: 'y',
            default: 0,
            format: NumberInputFormat.Length,
          },
          z: {
            type: 'number',
            title: 'z',
            default: 0,
            format: NumberInputFormat.Length,
          },
          zdir: {
            type: 'string',
            format: PickInputRJSFFormat.PickDirection,
          },
          length: {
            type: 'number',
            title: '长度',
            default: 100,
            format: NumberInputFormat.Length,
          },
          width: {
            type: 'number',
            title: '宽度',
            default: 100,
            format: NumberInputFormat.Length,
          },
          ...(formData.boxMode === BoxMode.BOX
            ? {
                height: {
                  type: 'number',
                  title: '高度',
                  default: 100,
                  format: NumberInputFormat.Length,
                },
              }
            : {}),
          ...(formData.boxMode === BoxMode.FLAT && zID === 'YOZ'
            ? {}
            : {
                nx: {
                  type: 'number',
                  title: 'Nx横向点数',
                  default: 2,
                  minimum: 2,
                  maximum: 150,
                  format: NumberInputFormat.Real,
                },
              }),
          ...(formData.boxMode === BoxMode.FLAT && zID === 'XOZ'
            ? {}
            : {
                ny: {
                  type: 'number',
                  title: 'Ny纵向点数',
                  default: 2,
                  minimum: 2,
                  maximum: 150,
                  format: NumberInputFormat.Real,
                },
              }),
          ...(formData.boxMode === BoxMode.FLAT && zID === 'XOY'
            ? {}
            : {
                nz: {
                  type: 'number',
                  title: 'Nz竖向点数',
                  default: 2,
                  minimum: 2,
                  maximum: 150,
                  format: NumberInputFormat.Real,
                },
              }),
        },
      };
    }
    return schema;
  }, [formData.postype, formData.inputMode, formData.boxMode, formData.zdir]);

  return (
    <PickableCommandForm
      isFormDataValid={finishValid}
      disabled={false}
      onClose={onClose}
      title={title}
      schema={schema}
      formData={formData}
      onChange={(v) => {
        setFormData(v);
      }}
      extraUiSchema={{
        origin: {
          'ui:placeholder': '中心',
          'ui:autofocus': true,
        },
        zdir: {
          'ui:placeholder': '方向',
        },
        length: {
          'ui:options': {
            showReverse: formData.postype === PosType.BOTTOMVERTEX,
            onReverseChange: (isReverse) => {
              setFormData((prevFormData) => ({
                ...prevFormData,
                reverseLength: isReverse,
              }));
            },
          },
        },
        width: {
          'ui:options': {
            showReverse: formData.postype === PosType.BOTTOMVERTEX,
            onReverseChange: (isReverse) => {
              setFormData((prevFormData) => ({
                ...prevFormData,
                reverseWidth: isReverse,
              }));
            },
          },
        },
        height: {
          'ui:options': {
            showReverse: formData.postype !== PosType.CENTER,
            onReverseChange: (isReverse) => {
              setFormData((prevFormData) => ({
                ...prevFormData,
                reverse: isReverse,
              }));
            },
          },
        },
      }}
      submitText='完成'
      onSubmit={checkAndFinish}
    />
  );
}

export const cmdElecBox: FeatureBaseCommand = {
  id: 'CmdElecBox',
  label: 'command.elecBox',
  icon: 'commands/feature/box',
  create: (label, onClose, props) => (
    <ElecBox
      title={i18n.t(label)}
      onClose={onClose}
      props={props}
    />
  ),
};
